package top.xana.acg.anime.utils

import android.content.Context
import android.content.res.Resources
import android.content.res.TypedArray
import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.util.Log
import android.util.TypedValue
import android.view.View
import android.widget.Toast
import androidx.annotation.ColorRes
import androidx.annotation.DimenRes
import androidx.annotation.DrawableRes
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.core.content.res.ResourcesCompat
import androidx.core.view.ViewCompat
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.RecyclerView
import com.google.gson.reflect.TypeToken
import com.skydoves.sandwich.ApiResponse
import com.skydoves.sandwich.message
import com.skydoves.sandwich.suspendOnSuccess
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.json.JSONException
import org.json.JSONObject
import top.xana.acg.anime.App
import top.xana.acg.anime.App.Companion.TAG
import top.xana.acg.anime.network.response.Response

import java.io.IOException
import java.io.InputStream
import java.io.Serializable
import java.util.*

const val DEFAULT_TAG = "atri_tag"

//region Get value from XML
fun Context.getDimenInt(@DimenRes resId: Int): Int = resources.getDimension(resId).toInt()

fun Context.getDimenFloat(@DimenRes resId: Int): Float = resources.getDimension(resId)

fun Context.getColorRes(@ColorRes resColor: Int): Int =
    ResourcesCompat.getColor(resources, resColor, theme)

fun Context.getDrawableRes(@DrawableRes resDrawable: Int): Drawable? =
    ResourcesCompat.getDrawable(resources, resDrawable, theme)

fun View.getColorRes(@ColorRes resColor: Int) = context.getColorRes(resColor)
//endregion

//region To dp, sp, px
val Float.dp
    get() = TypedValue.applyDimension(
        TypedValue.COMPLEX_UNIT_DIP,
        this,
        Resources.getSystem().displayMetrics
    )

val Float.sp
    get() = TypedValue.applyDimension(
        TypedValue.COMPLEX_UNIT_SP,
        this,
        Resources.getSystem().displayMetrics
    )

val Float.px
    get() = TypedValue.applyDimension(
        TypedValue.COMPLEX_UNIT_PX,
        this,
        Resources.getSystem().displayMetrics
    )

val Float.px2Dp: Float
    get() {
        val density = Resources.getSystem().displayMetrics.density
        return (this / density + 0.5).toFloat()
    }

val Int.dp
    get() = TypedValue.applyDimension(
        TypedValue.COMPLEX_UNIT_DIP,
        this.toFloat(),
        Resources.getSystem().displayMetrics
    ).toInt()

val Int.sp
    get() = TypedValue.applyDimension(
        TypedValue.COMPLEX_UNIT_SP,
        this.toFloat(),
        Resources.getSystem().displayMetrics
    ).toInt()

val Int.px
    get() = TypedValue.applyDimension(
        TypedValue.COMPLEX_UNIT_PX,
        this.toFloat(),
        Resources.getSystem().displayMetrics
    ).toInt()

val Int.px2Dp: Int
    get() {
        val density = Resources.getSystem().displayMetrics.density
        return (this / density + 0.5).toInt()
    }
//endregion

//region Common tools
// toast
fun Context.toast(msg: String, duration: Int = Toast.LENGTH_SHORT) {
    Toast.makeText(this, msg, duration).show()
}

fun Context.toast(msg: String, duration: Int = Toast.LENGTH_SHORT, showApp: Boolean = false) {
    if (showApp) {
        toast(msg, duration)
    } else {
        Toast.makeText(this, null, duration).apply {
            setText(msg)
        }.show()
    }

}

fun Context.toast(msgRes: Int, duration: Int = Toast.LENGTH_SHORT) {
    Toast.makeText(this, msgRes, duration).show()
}

// 序列化｜反序列化
fun Any.toJsonString(): String? = GsonUtils.toJson(this)

inline fun <reified T> parseObjectOrNull(data: String?): T? {
    val type = object : TypeToken<T>() {}.type
    return GsonUtils.safeParseJson(data, type)
}

// 流转字符串
fun InputStream.convertToString(): String {
    var s = ""
    try {
        val scanner = Scanner(this, Charsets.UTF_8.name()).useDelimiter("\\A")
        if (scanner.hasNext()) {
            s = scanner.next()
        }
    } catch (e: IOException) {
        throw e
    } finally {
        close()
    }
    return s
}

// about views
fun View.setShadowBackground(bgDrawable: Drawable) {
    setLayerType(View.LAYER_TYPE_SOFTWARE, null)
    ViewCompat.setBackground(this, bgDrawable)
}

inline fun <R> TypedArray.safeUse(block: TypedArray.() -> R): R? {
    return try {
        block()
    } catch (e: Exception) {
        Log.e(DEFAULT_TAG, "Error occurred while use typed array...${e.message}")
        null
    } finally {
        recycle()
    }
}

// ConstraintSet apply 到同一个 view 上
inline fun ConstraintSet.applyToTheSame(
    view: ConstraintLayout,
    block: ConstraintSet.() -> Unit
) {
    this.apply { clone(view) }.apply {
        block()
    }.applyTo(view)
}

fun <T> unlockLazy(initializer: () -> T) = lazy(LazyThreadSafetyMode.PUBLICATION, initializer)
//endregion

//region About list
fun <T> MutableList<T>.swap(first: Int, second: Int) {
    val tmp = this[first]
    this[first] = this[second]
    this[second] = tmp
}

//endregion

//region JSONObject
inline fun JSONObject.applyWithCatch(block: JSONObject.() -> Unit) {
    try {
        this.block()
    } catch (e: JSONException) {
        Log.e(DEFAULT_TAG, "Something went wrong when manipulate JSONObject: ${e.message}")
    }
}
//endregion

//region RecyclerView

/**
 * Check whether the view is available now.
 * available -> smoothScrollBy()
 * not available -> smoothScrollToPosition()
 */
fun RecyclerView.scrollToTargetFinal(target: Int) {
    post {
        findViewHolderForLayoutPosition(target)?.itemView?.let { view -> smoothScrollBy(view) }
            ?: smoothScrollToPosition(target) // give up.jpg
    }
}


fun RecyclerView.smoothScrollBy(targetView: View) {
    val targetPos = intArrayOf(0, 0)
    targetView.getLocationOnScreen(targetPos)
    val rvRect = Rect()
    getGlobalVisibleRect(rvRect)
    val gapY = targetPos[1] - rvRect.top // targetViewTop to recyclerViewTop
    smoothScrollBy(0, gapY, null, 600)
}


/**
 * Scroll the list upward.
 *
 * @param itemView  The item view that we need to display on top. Null if the view is not on
 * screen.
 * @param target    The target item index.
 */
fun RecyclerView.scrollUpward(itemView: View?, target: Int) {
    if (itemView != null) {
        smoothScrollBy(itemView)
        return
    }
    scrollToPosition(target + 1)
    post { scrollTo(0, (-8).dp) }
    scrollToTargetFinal(target)
}

/**
 * Scroll the list downward.
 *
 * @param itemView  The item view that we need to display on top. Null if the view is not on
 * screen.
 * @param target    The target item index.
 */
fun RecyclerView.scrollDownward(itemView: View?, target: Int) {
    if (itemView != null) {
        smoothScrollBy(itemView)
        return
    }
    val lastItem = target - 1
    // first to this view.
    // maybe the view is not long as the screen,
    // it will display at the bottom of screen;
    // if it is so long that the screen can't contain all the item,
    //
    scrollToPosition(lastItem)
    post {
        // make sure bottom view of lastItem is at the bottom of the screen
        // Though we can use lastView?.top to get invisibleTop,
        // but that relies on another set of coordinate system,
        // so se still use Method lastView?.getLocationOnScreen()
        // to get the absolute Y of the lastView.
        val lastView = findViewHolderForLayoutPosition(lastItem)?.itemView
        val lastViewVisibleRect = Rect()
        val lastViewLocationOnScreen = intArrayOf(0, 0)
        lastView?.getLocationOnScreen(lastViewLocationOnScreen)
        lastView?.getGlobalVisibleRect(lastViewVisibleRect) // get visible rect
        val lastHeight = lastView?.height ?: 0
        val lastInvisibleTop = (lastViewVisibleRect.top - lastViewLocationOnScreen[1])
            .takeIf { it >= 0 } ?: 0
        val lastInvisibleBottom = (lastHeight - lastInvisibleTop - lastViewVisibleRect.height())
            .takeIf { it >= 0 } ?: 0
        val offsetY = lastInvisibleBottom + 8.dp
        scrollBy(0, offsetY)
    }
    scrollToTargetFinal(target)
}


//endregion
public data class Quattro<out A, out B, out C, out D>(
    public val first: A,
    public val second: B,
    public val third: C,
    public val forth: D
) : Serializable {
    /**
     * Returns string representation of the [Triple] including its [first], [second] and [third] values.
     */
    override fun toString(): String = "($first, $second, $third,$forth)"
}

fun Any.LogE(msg: String?, tag: String? = null, t: Throwable? = null) {
    Log.e(tag ?: javaClass.simpleName, msg ?: "", t)
}

//endregion

fun Any?.toString() = "${this?.javaClass?.simpleName} #${GsonUtils.toJson(this ?: "")}"


//region #response
suspend inline fun <T : Response<*>> ApiResponse<T>.onSuccess(
    crossinline onResult: suspend ApiResponse.Success<T>.() -> Unit
): ApiResponse<T> = suspendOnSuccess {
    if (data.ok()) {
        onResult(this)
    }
}

suspend inline fun <T : Response<*>> ApiResponse<T>.onFailure(
    crossinline onResult: suspend ApiResponse<T>.(msg: String) -> Unit
): ApiResponse<T> {
    when (this) {
        is ApiResponse.Success -> if (!data.ok()) {
            val msg = "${data.msg()}, code:${data.code}"
            LogE(msg, TAG)
            onResult(this, msg)
            withContext(Dispatchers.Main) {
                ToastUtils.showShort(msg)
            }
        }

        is ApiResponse.Failure.Error -> {
            val errBody = GsonUtils.safeParseJson<Response<Unit>>(errorBody?.string())
            val msg = "code[${errBody?.code}, ${errBody?.msg()}"
            onResult(this, msg)
            withContext(Dispatchers.Main) {
                ToastUtils.showShort(msg)
            }
        }

        is ApiResponse.Failure -> {
            val msg = message()
            LogE(msg, TAG)
            onResult(this, msg)
            withContext(Dispatchers.Main) {
                ToastUtils.showShort(msg)
            }
        }
    }
    return this
}
//endregion

fun View.updateScale(sx: Float = scaleX, sy: Float = scaleY) {
    scaleX = sx
    scaleY = sy

}

fun RecyclerView.removeAllDecorations() = apply {
    for (i in 0 until itemDecorationCount) {
        removeItemDecorationAt(0)
    }
}

val Fragment.contextNotNull: Context
    get() = context ?: App.app.applicationContext


inline fun <reified T> Any?.cast() = try {
    check(this is T) { "this is not ${T::class.java.simpleName}" }
    this
} catch (t: Throwable) {
    this?.LogE("Fail to cast: ${t.message}", "CastError")
    null
}